#include "TwoWayList.h"
#include "Record.h"
#include "Schema.h"
#include "File.h"
#include "Comparison.h"
#include "ComparisonEngine.h"
#include "DBFile.h"
#include "Defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>


FileMetadata::FileMetadata(){

}

void FileMetadata::SetMetaData(fType type){
	mydata.myType = type;
}

//File name is generated in same directory as a hidden file with .meta extension.
//Example: DB File: cutsomer.bin, Metadata file: .customer.bin.meta
void FileMetadata::GenerateMetadataName(char* f_path,char** f_metadata){
	*f_metadata = new char[strlen(f_path)+sizeof(".meta")];
	memcpy(*f_metadata,f_path,strlen(f_path));
	memcpy(*f_metadata+strlen(f_path),".meta",sizeof(".meta"));
	//cout << "Meta file name is: " << *f_metadata << "\n";
	return;
}

//The structure FileMetadata.myData is written into the file. Uses
//FileMetaDataLLGenerateMetaDataName to generate file name.
int FileMetadata::WriteMetaData(char* f_path){
	char* f_metadata;
	GenerateMetadataName(f_path,&f_metadata);
	int flags = O_TRUNC | O_RDWR | O_CREAT;
	int mode = S_IRUSR | S_IWUSR;
	int fileCreate = open(f_metadata,flags,mode);
	if(fileCreate < 0){
		cerr << "MBAD!  Open did not work for " << f_metadata << "\n";
		exit(1);
	}
	delete f_metadata;
	int ret = write(fileCreate,reinterpret_cast<char*>(&mydata),sizeof(mydata));
	close(fileCreate);
	return ret;
}

//Loads data into structure FileMetaData.myData
int FileMetadata::LoadMetaData(char* f_path){
	char* f_metadata;
	GenerateMetadataName(f_path,&f_metadata);

	int flags = O_RDONLY;
	int mode = S_IRUSR;
	int fileCreate = open(f_metadata,flags,mode);
	if(fileCreate < 0){
		cerr << "LMBAD!  Open did not work for " << f_metadata << "\n";
		exit(1);
	}
	delete f_metadata;
	int ret = read(fileCreate,reinterpret_cast<char*>(&mydata),sizeof(mydata));

	close(fileCreate);
	return ret;
}

int DBFile::writeDirtyPage(){
	if(isDirty){
		if(!myFile->AddPage(myPage,currentPage)){
			cerr << "Can not write dirty page!!\n";
			exit(1);
		}
		isDirty = false;
		isAddDone = false;
	}
	return 1;
}

//Creates an object of File, Page and Record class.
DBFile::DBFile () {
	myFile = new File();
	myPage = new Page();
	currentRecord = NULL;
	currentPage = -1;
	lastReadPage = -1;
	lastReadRecord = 0;
	isDirty = false;
	isAddDone = false;
}

DBFile::~DBFile () {
	delete myFile;
	delete myPage;
}

// Creates the file and keeps it OPEN.
// It is not closed based on usage scenario in test1() of file test.cc which does DBFile::Load()
// immediately after Create()
//TODO: Analyze case where Create/Open is called in between handling a file
//i.e., a sequence like Open, MoveFirst, GetNextRecord, Add, Open
int DBFile::Create (char* f_path, fType f_type, void* startup) {
	if(f_path == NULL){
		return 0;
	}
	myFile->Open(0,f_path);
	//Set the metadata
	myMetadata.SetMetaData(f_type);
	//Write meta data into metadata file
	if(myMetadata.WriteMetaData(f_path) < 0){
		cerr << "BAD!  Metadata file could not be written into\n";
		exit(1);
	}
	currentPage = -1;
	lastReadPage = -1;
	lastReadRecord = 0;
	isDirty = false;
	isAddDone = false;
	return 1;
}

//Text file -> binary file
void DBFile::Load (Schema &f_schema, char *loadpath) {
	if(loadpath == NULL){
		cerr << "BAD! Specified load path is incorrect\n";
		exit(1);
	}

	Record record;
	FILE *tableFile = fopen (loadpath, "r");
	if(tableFile == NULL){
		cerr << "BAD! File could not be opened: " << loadpath << "\n";
		exit(1);
	}

	while(record.SuckNextRecord(&f_schema,tableFile)){
		this->Add(record);
	}
	//myFile->AddPage(myPage,currentPage);
	fclose(tableFile);
}

//File is opened and metadata is loaded from the .meta file
int DBFile::Open (char *f_path) {
	if(f_path == NULL){
			return 0;
	}
	myFile->Open(1,f_path);
	if(myMetadata.LoadMetaData(f_path) < 0){
		cerr << "BAD!  Metadata file could not be written into\n";
		exit(1);
	}
	currentPage = -1;
	lastReadPage = -1;
	lastReadRecord = 0;
	isDirty = false;
	isAddDone = false;
	return 1;
}

void DBFile::MoveFirst () {
	//Check if current page is dirty and write into file
	if(currentPage != 0){
		writeDirtyPage();
		currentPage = 0;
		lastReadPage = 0;
		lastReadRecord = 0;
		isAddDone = false;
		myFile->GetPage(myPage,currentPage);
		myPage->GetFirst(currentRecord,false);
	}
}

//myFile->Close returns the number of pages in it and can be zero. So DONT use it as return value.
int DBFile::Close () {
	//Check if current page is dirty and write into file
	writeDirtyPage();
	myFile->Close();
	return 1;
}

//record is deleted by myPage->Append
void DBFile::Add (Record &record) {
	//NOTE: this variable is lastPage. not to be confused with lastReadPage
	off_t lastPage = myFile->GetLength() - 2;
	if((lastPage >= 0) && (lastPage > currentPage)){
		myFile->GetPage(myPage,lastPage);
		currentPage = lastPage;
	} else if(lastPage == -2){
		currentPage = 0;
	}

	if(!myPage->Append(&record)){
		writeDirtyPage();
		currentPage++;
		myPage->EmptyItOut();
		myPage->Append(&record);
	}
	isDirty = true;
	isAddDone = true;
}

int DBFile::GetNext (Record &fetchme) {
	//cout << "My last page:" << lastReadPage << " Current page:"<< currentPage << " LastReadRecord:" <<lastReadRecord << "\n";
	if((lastReadPage > 0) && (lastReadPage != currentPage)){
		writeDirtyPage();
		myFile->GetPage(myPage,lastReadPage);
		currentPage = lastReadPage;
		myPage->GoToRecord(lastReadRecord);
	} else {
		if(isAddDone){
			myPage->GoToRecord(lastReadRecord);
			isAddDone = false;
		}
	}

	if(!myPage->GetNextRecord(fetchme)){
		writeDirtyPage();
		if((currentPage+1) < (myFile->GetLength()-1)){
			currentPage++;
			myFile->GetPage(myPage,currentPage);
			lastReadPage = currentPage;
			lastReadRecord = 0;
			myPage->GetFirst(currentRecord,false);
			myPage->GetNextRecord(fetchme);

		} else {
			return 0;
		}
	}
	lastReadRecord++;
	return 1;
}

int DBFile::GetNext (Record &fetchme, CNF &cnf, Record &literal) {
	ComparisonEngine comp;
	do{
		if(this->GetNext(fetchme) == 0){
				return 0;
		}
	} while(!comp.Compare(&fetchme,&literal,&cnf));

	return 1;
}
